﻿using SharpDX.D3DCompiler;
using SharpDX.Direct3D11;
using System;
using System.IO;

namespace Engine.Graphics.Shaders
{
    public class IncludeFX : Include
    {
        #region Private Fields

        private static string includeDirectory = ".\\Shaders\\";
        private System.IO.Stream stream;

        #endregion Private Fields

        #region Public Properties

        public IDisposable Shadow
        {
            get
            {
                return this;
            }
            set
            {
            }
        }

        #endregion Public Properties

        #region Public Methods

        public void Close(System.IO.Stream stream)
        {
            stream.Close();
            stream.Dispose();
        }

        public void Dispose()
        {
            this.stream.Close();
            this.stream.Dispose();
            this.stream = null;
        }

        public void Open(IncludeType type, string fileName, System.IO.Stream parentStream, out System.IO.Stream stream)
        {
            stream = new System.IO.FileStream(includeDirectory + fileName, System.IO.FileMode.Open);
        }

        public System.IO.Stream Open(IncludeType type, string fileName, System.IO.Stream parentStream)
        {
            return stream = new System.IO.FileStream(includeDirectory + fileName, System.IO.FileMode.Open);
        }

        #endregion Public Methods
    }

    public class ShaderBase
    {
        #region Public Fields

        public InputLayout inputLayout;
        public PixelShader ps;
        public ShaderBytecode shaderByteCodePS;
        public ShaderBytecode shaderByteCodeVS;
        public VertexShader vs;

        #endregion Public Fields

        #region Internal Fields

        internal static uint lastHashcodeMappingMatrix1 = 0;
        internal static uint lastHashcodeMappingMatrix2 = 0;
        internal static uint lastHashcodeMappingMatrix3 = 0;
        internal Device device;

        #endregion Internal Fields

        #region Private Fields

        private static int lastSamplerHash1;
        private static int lastSamplerHash2;
        private static int lastSamplerHash3;
        private static int lastTextureID1 = -1;
        private static int lastTextureID2 = -1;
        private static int lastTextureID3 = -1;
        private int hashCodeLastShader = -1;

        #endregion Private Fields

        #region Public Constructors

        public ShaderBase(Device renderDevice)
        {
            this.device = renderDevice;
        }

        #endregion Public Constructors

        #region Public Properties

        public ShaderBase effect { get { return this; } }

        #endregion Public Properties

        #region Public Methods

        public void LoadShader(Device device, string fileName, string PixelShaderName, string VertexShaderName)
        {
            ShaderFlags shaderflags = ShaderFlags.OptimizationLevel3;

#if DEBUG
            shaderflags = ShaderFlags.Debug | ShaderFlags.SkipOptimization;
#else
            shaderflags = ShaderFlags.OptimizationLevel3;
#endif

            string pathWithoutExtension = Path.GetDirectoryName(fileName) + "\\" + Path.GetFileNameWithoutExtension(fileName);
            string filenameVSCompiles = pathWithoutExtension + "VS.CSO";
            string filenamePSCompiles = pathWithoutExtension + "PS.CSO";

            if (File.Exists(filenameVSCompiles)) // Load VertexShader
            {
                shaderByteCodeVS = ShaderBytecode.FromFile(filenameVSCompiles);
            }
            else
            {
                IncludeFX include = new IncludeFX();
                shaderByteCodeVS = ShaderBytecode.CompileFromFile(fileName, VertexShaderName, "vs_4_0", shaderflags, EffectFlags.None, null, include);
            }

            if (File.Exists(filenamePSCompiles)) // Load PixelShader
            {
                shaderByteCodePS = ShaderBytecode.FromFile(filenamePSCompiles);
            }
            else
            {
                IncludeFX include = new IncludeFX();
                shaderByteCodePS = ShaderBytecode.CompileFromFile(fileName, PixelShaderName, "ps_4_0", shaderflags, EffectFlags.None, null, include);
            }

            vs = new VertexShader(device, shaderByteCodeVS.Data);
            ps = new PixelShader(device, shaderByteCodePS.Data);

            this.ps.DebugName = filenamePSCompiles;
            this.vs.DebugName = filenameVSCompiles;
        }

        public void SetSamplerState(SamplerState samplerState, int slot)
        {
            ShaderBase.lastSamplerHash3 = -1;
            this.device.ImmediateContext.PixelShader.SetSampler(slot, samplerState);
        }

        public void SetSamplerState1(SamplerState samplerState)
        {
            int newHashCode = samplerState.GetHashCode();
            if (ShaderBase.lastSamplerHash1 != newHashCode)
            {
                ShaderBase.lastSamplerHash1 = newHashCode;
                this.device.ImmediateContext.PixelShader.SetSampler(0, samplerState);
            }
        }

        public void SetSamplerState2(SamplerState samplerState)
        {
            int newHashCode = samplerState.GetHashCode();
            if (ShaderBase.lastSamplerHash2 != newHashCode)
            {
                ShaderBase.lastSamplerHash2 = newHashCode;
                this.device.ImmediateContext.PixelShader.SetSampler(1, samplerState);
            }
        }

        public void SetSamplerState3(SamplerState samplerState)
        {
            int newHashCode = samplerState.GetHashCode();
            if (ShaderBase.lastSamplerHash3 != newHashCode)
            {
                ShaderBase.lastSamplerHash3 = newHashCode;
                this.device.ImmediateContext.PixelShader.SetSampler(2, samplerState);
            }
        }

        public void SetTexture(int slot, ShaderResourceView resource)
        {
            if (slot == 0) ShaderBase.lastTextureID1 = -1;
            else if (slot == 1) ShaderBase.lastTextureID2 = -1;
            else if (slot == 2) ShaderBase.lastTextureID3 = -1;
            this.device.ImmediateContext.PixelShader.SetShaderResource(slot, resource);
        }

        public void SetTexture1(string textureName, MaterialManager materialManager)
        {
            if (ShaderBase.lastTextureID1 != materialManager.GetTextureID(textureName))
            {
                ShaderBase.lastTextureID1 = materialManager.GetTextureID(textureName);
                lock (materialManager.Engine.Device.ImmediateContext)
                    materialManager.Engine.Device.ImmediateContext.PixelShader.SetShaderResource(0, materialManager.Textures[lastTextureID1]);
            }
        }

        public void SetTexture2(string textureName, MaterialManager materialManager)
        {
            if (ShaderBase.lastTextureID2 != materialManager.GetTextureID(textureName))
            {
                ShaderBase.lastTextureID2 = materialManager.GetTextureID(textureName);
                materialManager.Engine.Device.ImmediateContext.PixelShader.SetShaderResource(1, materialManager.Textures[lastTextureID2]);
            }
        }

        public void SetTexture3(string textureName, MaterialManager materialManager)
        {
            if (ShaderBase.lastTextureID3 != materialManager.GetTextureID(textureName))
            {
                ShaderBase.lastTextureID3 = materialManager.GetTextureID(textureName);
                materialManager.Engine.Device.ImmediateContext.PixelShader.SetShaderResource(2, materialManager.Textures[lastTextureID3]);
            }
        }

        #endregion Public Methods

        #region Internal Methods

        internal static void ResetShaderValues()
        {
            ShaderBase.lastHashcodeMappingMatrix1 = 0;
            ShaderBase.lastHashcodeMappingMatrix2 = 0;
            ShaderBase.lastHashcodeMappingMatrix3 = 0;
            ShaderBase.lastTextureID1 = -1;
            ShaderBase.lastTextureID2 = -1;
            ShaderBase.lastTextureID3 = -1;
            ShaderBase.lastSamplerHash1 = -1;
            ShaderBase.lastSamplerHash2 = -1;
            ShaderBase.lastSamplerHash3 = -1;
        }

        internal void Dispose()
        {
            if (this.inputLayout != null)
                this.inputLayout.Dispose();
            this.inputLayout = null;
            if (this.ps != null)
                this.ps.Dispose();
            this.ps = null;
            if (this.vs != null)
                this.vs.Dispose();
            this.vs = null;
            if (this.shaderByteCodeVS != null)
                this.shaderByteCodeVS.Dispose();
            this.shaderByteCodeVS = null;
            if (this.shaderByteCodePS != null)
                this.shaderByteCodePS.Dispose();
            this.shaderByteCodePS = null;
        }

        #endregion Internal Methods
    }
}